#region License
//-----------------------------------------------------------------------------
// Copyright (c) 2008, Aaron MacDougall, Daniel Jeffery
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of Aaron MacDougall or Daniel Jeffery nor the names of its contributors may
//   be used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
#endregion

namespace Agro2D
{
    /// <summary>
    /// Abstracts some of the common input methods to one common interface.
    /// Also provides action mapping functionality.
    /// </summary>
    public class InputManager
    {
        private enum ButtonInputType
        {
            Buttons,
            Keys
        }

        private class ButtonAction
        {
            public ButtonInputType InputType;
            public Buttons Buttons;
            public Keys Keys;
        }

        private static readonly InputManager instance = new InputManager();

        private Dictionary<string, ButtonAction>[] actions;
        private bool swapSticks;
        private bool swapTriggers;
        private float timeSinceLastKey;

        public static InputManager Instance
        {
            get { return instance; }
        }

        public bool SwapSticks
        {
            get { return this.swapSticks; }
            set { this.swapSticks = value; }
        }

        public bool SwapTriggers
        {
            get { return this.swapTriggers; }
            set { this.swapTriggers = value; }
        }

        public InputManager()
        {
            this.actions = new Dictionary<string, ButtonAction>[4];
            for (int i = 0; i < 4; i++)
                this.actions[i] = new Dictionary<string, ButtonAction>();

            this.swapSticks = false;
            this.swapTriggers = false;
            this.timeSinceLastKey = 0;
        }

        public void AddAction(PlayerIndex playerIndex, string actionName, Buttons button)
        {
            Debug.Assert(actionName.Length > 0);

            ButtonAction newAction = new ButtonAction();
            newAction.InputType = ButtonInputType.Buttons;
            newAction.Buttons = button;

            this.actions[GetPlayerIndex(playerIndex)].Add(actionName, newAction);
        }

        public void AddAction(PlayerIndex playerIndex, string actionName, Keys key)
        {
            Debug.Assert(actionName.Length > 0);

            ButtonAction newAction = new ButtonAction();
            newAction.InputType = ButtonInputType.Keys;
            newAction.Keys = key;

            this.actions[GetPlayerIndex(playerIndex)].Add(actionName, newAction);
        }

        public virtual bool CanChangeOption()
        {
            if(timeSinceLastKey < 0.2)
                return false;

            timeSinceLastKey = 0;
            return true;
        }

        public void RemoveAction(PlayerIndex playerIndex, string actionName)
        {
            this.actions[GetPlayerIndex(playerIndex)].Remove(actionName);
        }

        public ButtonState GetButtonState(PlayerIndex playerIndex, string actionName)
        {
            Debug.Assert(actionName.Length > 0);
            Debug.Assert(this.actions[GetPlayerIndex(playerIndex)].ContainsKey(actionName));

            ButtonAction action = this.actions[GetPlayerIndex(playerIndex)][actionName];

            ButtonState state = ButtonState.Released;

            if (action.InputType == ButtonInputType.Buttons)
            {
                if (GamePad.GetState(PlayerIndex.One).IsButtonDown(action.Buttons))
                    state = ButtonState.Pressed;
            }
            else if (action.InputType == ButtonInputType.Keys)
            {
                if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(action.Keys))
                    state = ButtonState.Pressed;
            }

            return state;
        }

        public bool IsActionDown(PlayerIndex playerIndex, string actionName)
        {
            if(GetButtonState(playerIndex, actionName) == ButtonState.Pressed)
                return true;
            else
                return false;
        }

        public Vector2 GetLeftStickValue(PlayerIndex playerIndex)
        {
            return GetStickValue(playerIndex, true);
        }

        public Vector2 GetRightStickValue(PlayerIndex playerIndex)
        {
            return GetStickValue(playerIndex, false);
        }

        private Vector2 GetStickValue(PlayerIndex playerIndex, bool leftStick)
        {
            if (this.swapSticks)
                leftStick = !leftStick;

            if (leftStick)
                return GamePad.GetState(playerIndex).ThumbSticks.Left;
            else
                return GamePad.GetState(playerIndex).ThumbSticks.Right;
        }

        public float GetLeftTriggerValue(PlayerIndex playerIndex)
        {
            return GetTriggerValue(playerIndex, true);
        }

        public float GetRightTriggerValue(PlayerIndex playerIndex)
        {
            return GetTriggerValue(playerIndex, false);
        }

        private float GetTriggerValue(PlayerIndex playerIndex, bool leftTrigger)
        {
            if (this.swapTriggers)
                leftTrigger = !leftTrigger;

            if (leftTrigger)
                return GamePad.GetState(playerIndex).Triggers.Left;
            else
                return GamePad.GetState(playerIndex).Triggers.Right;
        }


        private uint GetPlayerIndex(PlayerIndex playerIndex)
        {
            switch (playerIndex)
            {
                case PlayerIndex.One:
                    return 0;
                case PlayerIndex.Two:
                    return 1;
                case PlayerIndex.Three:
                    return 2;
                case PlayerIndex.Four:
                    return 3;
            }

            return 0;
        }

        public void Update(GameTime gameTime)
        {
            timeSinceLastKey += (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
    }
}